using System.Collections.Generic;
using Tycho.Parser.Tokens;

namespace Tycho.Parser.Peg {
    public class NonTerminal<Context> : Production<Context> {
        private readonly AssociationSwapper AssociationSwapper;
        private readonly HashSet<string> NamedMultiples;
        public string Name;
        public Rule<Context> Rule;

        public NonTerminal (string name, Rule<Context> rule)
            : this (name, rule, null, null) {
        }

        public NonTerminal (string name, Rule<Context> rule, IEnumerable<string> namedMultiples)
            : this (name, rule, namedMultiples, null) {
        }

        public NonTerminal (string name, Rule<Context> rule, IEnumerable<string> namedMultiples, AssociationSwapper associationSwapper) {
            Name = name;
            Rule = rule;

            if (namedMultiples != null) {
                NamedMultiples = new HashSet<string> (namedMultiples);
            } else {
                namedMultiples = null;
            }

            AssociationSwapper = associationSwapper;
        }

        protected override ParseResult<Context> ReallyParse (Token[] tokens, int index, Context context) {
            RuleParseResult<Context> parseResult = Rule.Parse (tokens, index, context);

            if (parseResult != null) {
                var term = new CompositeTerm (Name);

                foreach (Capture capture in parseResult.Captures) {
                    term.Add (capture.Name, capture.Term, IsMultiple (capture.Name));
                }

                if (NamedMultiples != null) {
                    foreach (string captureName in NamedMultiples) {
                        if (!term.SubTerms.ContainsKey (captureName)) {
                            term.Add (captureName, new ListTerm (), false);
                        }
                    }
                }

                CompositeTerm resultTerm;
                if (AssociationSwapper != null) {
                    resultTerm = AssociationSwapper.Swap (term);
                } else {
                    resultTerm = term;
                }

                return new ParseResult<Context> (parseResult.Index, resultTerm, parseResult.Context);
            } else {
                return null;
            }
        }

        private bool IsMultiple (string name) {
            return NamedMultiples != null ? NamedMultiples.Contains (name) : false;
        }
    }
}